Laboratorio 2: Exploración y Visualización de Datos¶

Fecha: Marzo 2025

Integrantes: Alina González (Sec 1) y Sergio Romero (Sec 1)

Instrucciones¶

  1. Trabajen en equipos de dos personas. Salvo excepciones, no se corregirá entregas con menos de dos integrantes.

  2. Modifique este archivo .ipynb agregando sus respuestas donde corresponda. Puede ocupar Jupyter notebook en su computador o usar Google Colab como alternativa online.

  3. Para cada pregunta incluya el código fuente que utilizó para llegar a su respuesta. Respuestas sin código no recibirán puntaje..

  4. El formato de entrega para esta actividad es un archivo html. Genere un archivo HTML usando Jupyter y súbalo a U-Cursos. Basta con que un/a integrante haga la entrega. Si ambos/as hacen una entrega en U-Cursos, se revisará cualquiera de éstas.

Accidentes de tránsito¶

Para esta sección utilizaremos un dataset real de número de accidentes de tránsito por localidad, el cual puede ser encontrado en el siguiente link: http://datos.gob.cl/dataset/9348. Para cargar el dataset ejecute el siguiente código:

In [37]:
import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.io as pio
In [38]:
tipos = pd.read_csv("https://users.dcc.uchile.cl/~hsarmien/mineria/datasets/accidentes_2010_2011.txt", sep=" ")
tipos.head()
Out[38]:
Muestra Descripcion Anio TipoAccidente Cantidad
1 Nacional Nacional 2010 Atropello 8247
2 Nacional Nacional 2011 Atropello 8339
3 Regional XV Región Arica y Parinacota 2010 Atropello 115
4 Regional XV Región Arica y Parinacota 2011 Atropello 159
5 Comunal ARICA 2010 Atropello 115

Explore el set de datos para responder las siguientes preguntas:

  1. ¿Cuáles son las dimensiones del dataset (filas, columnas)?
In [39]:
print(f"filas: {tipos.shape[0]} columnas: {tipos.shape[1]}")
filas: 4296 columnas: 5
  1. ¿Qué describe cada línea del dataset? (ejemplifique tomando el dato de la fila 235, extienda la descripción)
In [40]:
tipos.iloc[235]

# Cada linea describe la cantidad de accidentes en una muestra territorial de cierto tipo de accidente en un cierto año
# Ejemplo: la linea 235 que hubieron 6 atropellos en palmilla en el año 2011
Out[40]:
Muestra            Comunal
Descripcion       PALMILLA
Anio                  2011
TipoAccidente    Atropello
Cantidad                 6
Name: 236, dtype: object
  1. ¿Cuántos años diferentes abarca la información del dataset? Entregue un array con los años.
In [41]:
tipos['Anio'].unique()
Out[41]:
array([2010, 2011])
  1. Filtre los datos para incluir sólo los accidentes ocurridos el año 2011 a nivel Regional. Luego, genere un gráfico de barras que muestre la cantidad de accidentes en cada Región. Comente sus principales observaciones y si considera que es razonable usar el conteo de frecuencias para determinar las regiones más peligrosas en cuanto a accidentes de tránsito.

    OJO: hay que sumar la cantidad de accidentes para los distintos tipos de accidente de una misma región

In [42]:
afect2011 = tipos[(tipos['Muestra'] == 'Regional') & (tipos['Anio'] == 2011)]

plt.bar(afect2011['Descripcion'], afect2011['Cantidad'])

plt.xlabel('Descripción')
plt.ylabel('Cantidad')

plt.title('Cantidad de Muertos en Accidentes Regionales en 2011')

plt.xticks(rotation=45, ha='right')

plt.show()
No description has been provided for this image

R: La Región Metropolitana es la con mayor cantidad de accidentes, seguida de la región de Valparaíso. También hay 2 regiones VII. No es razonable el conteo de frecuencia para ver las regiones más peligrosas en accidentes de tránsito, ya que no considera población ni cantidad de autos en circulación.

  1. Filtre los datos para incluir sólo los accidentes ocurridos el año 2010 a nivel regional. Genere un boxplot donde se indique la cantidad de accidentes categorizado por tipo de accidente. ¿Cuáles son sus principales observaciones?
In [43]:
afect2010 = tipos[(tipos['Anio'] == 2010) & (tipos['Muestra'] == 'Regional')]

fig = px.box(afect2010, x='TipoAccidente', y='Cantidad')
fig.show(renderer='notebook')

R: Existen múltiples outliars debido a que en una región suceden muchos más accidentes de tránsito que en el resto.

  1. ¿Qué otra forma de explorar los datos podría agregar para el dataset de Accidentes de tránsito y qué información adicional aporta? Adjunte el código necesario.

R: Podrían revisarse las estadísticas de Cantidad con describe. Aporta datos sobre los cuales plantear preguntas sobre el dataset.

In [44]:
tipos.describe(include="all")
Out[44]:
Muestra Descripcion Anio TipoAccidente Cantidad
count 4296 4296 4296.000000 4296 4296.000000
unique 3 359 NaN 6 NaN
top Comunal PENAFLOR NaN Atropello NaN
freq 4104 12 NaN 716 NaN
mean NaN NaN 2010.500000 NaN 84.203911
std NaN NaN 0.500058 NaN 835.751218
min NaN NaN 2010.000000 NaN 0.000000
25% NaN NaN 2010.000000 NaN 1.000000
50% NaN NaN 2010.500000 NaN 5.000000
75% NaN NaN 2011.000000 NaN 20.000000
max NaN NaN 2011.000000 NaN 31487.000000

También podemos analizar la cantidad de accidentes por tipo en cada región y ver como se distribuyen.

In [ ]:
# for region in tipos[tipos['Muestra'] == 'Regional']['Descripcion'].unique():
#   df = tipos[tipos['Descripcion'] == region ].groupby('TipoAccidente', as_index=False)['Cantidad'].sum()

#   fig = px.line_polar(df, r='Cantidad', theta='TipoAccidente', line_close=True)

#   fig.update_layout(
#     title = f'Accidentes por tipo en {region}', font_size = 13, width=500, height=300
#   )


#   fig.show(renderer='notebook')

# DEJAMOS UN PURO GRÁFICO PORQUE SON MUY PESADOS, SI SON TODOS EL HTML PESA 150MB XD

df = tipos[tipos['Descripcion'] == 'Región Metropolitana' ].groupby('TipoAccidente', as_index=False)['Cantidad'].sum()

fig = px.line_polar(df, r='Cantidad', theta='TipoAccidente', line_close=True)

fig.update_layout(
  title = f'Accidentes por tipo en Región Metropolitana', font_size = 13, width=500, height=300
)

fig.show(renderer='notebook')

Los gráficos nos muestran que distintas regiones poseen distintas distribuiciones de accidentes de tráfico

Diabetes¶

Considere el set de datos de pacientes para la predicción de diabetes con las siguientes columnas:

  • gender: género del paciente
  • age: edad del paciente
  • hypertension: indica si el paciente tiene o no hipertensión
  • heart_disease: indica si el paciente tiene o no enfermedad cardiaca
  • smoking_history: indica si el paciente es o fue fumador
  • bmi: indice de masa corporal del paciente
  • HbA1c_level: Hemoglobina HbA1c del paciente
  • blood_glucose_level: Nivel de glucosa en sangre del paciente
  • diabetes: si el paciente tiene o no diabetes
In [46]:
diabetes = pd.read_csv("https://raw.githubusercontent.com/mzambrano1/Datasets-CC5205-otono-2023/master/lab1.2%202023-2/diabetes_prediction_dataset.csv")
diabetes.head()
Out[46]:
gender age hypertension heart_disease smoking_history bmi HbA1c_level blood_glucose_level diabetes
0 Female 80.0 0 1 never 25.19 6.6 140 0
1 Female 54.0 0 0 No Info 27.32 6.6 80 0
2 Male 28.0 0 0 never 27.32 5.7 158 0
3 Female 36.0 0 0 current 23.45 5.0 155 0
4 Male 76.0 1 1 current 20.14 4.8 155 0
  1. Para explorar el dataset, realice un análisis de frecuencias de los atributos categóricos (categorías binarias y multiclase).
In [47]:
for col in (['gender', 'hypertension', 'heart_disease', 'smoking_history', 'diabetes']):
    print('-'*80 + '\n')
    print(diabetes[col].value_counts())
    print('-'*80 + '\n')
--------------------------------------------------------------------------------

gender
Female    58552
Male      41430
Other        18
Name: count, dtype: int64
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------

hypertension
0    92515
1     7485
Name: count, dtype: int64
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------

heart_disease
0    96058
1     3942
Name: count, dtype: int64
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------

smoking_history
No Info        35816
never          35095
former          9352
current         9286
not current     6447
ever            4004
Name: count, dtype: int64
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------

diabetes
0    91500
1     8500
Name: count, dtype: int64
--------------------------------------------------------------------------------

  1. Muestre estadísticas de resumen para las variables numéricas y comenten sus observaciones.
In [48]:
diabetes[['age', 'bmi', 'HbA1c_level', 'blood_glucose_level']].describe()
Out[48]:
age bmi HbA1c_level blood_glucose_level
count 100000.000000 100000.000000 100000.000000 100000.000000
mean 41.885856 27.320767 5.527507 138.058060
std 22.516840 6.636783 1.070672 40.708136
min 0.080000 10.010000 3.500000 80.000000
25% 24.000000 23.630000 4.800000 100.000000
50% 43.000000 27.320000 5.800000 140.000000
75% 60.000000 29.580000 6.200000 159.000000
max 80.000000 95.690000 9.000000 300.000000

Clasificación¶

Ahora crearemos un clasificador binario (por ahora no importa cómo funciona), y veremos que tal es su desempeño decidiendo si una persona tiene diabetes o no.

In [49]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split

# Droppear categoricas
df_clf = diabetes.drop(columns=['gender', 'smoking_history'])
# Separar atributos y target
X = df_clf.drop(columns=['diabetes'])
y = df_clf['diabetes']
# Separar conjuntos de entrenamiento y test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
# Entrenar modelo
clf = DecisionTreeClassifier(max_depth=3, random_state=0)
clf.fit(X_train, y_train)
# Obtener predicciones
y_pred = clf.predict(X_test)

Veamos su matríz de confusión¶

In [50]:
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

cm = confusion_matrix(y_test, y_pred)

disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Sin diabetes', 'Diabetes'])
disp.plot(cmap='Blues')
plt.title('Matríz de Confusión')
plt.show()
No description has been provided for this image
  1. ¿Qué se puede decir a partir de la matriz de confusión? ¿El clasificador tiene un problema asociado a precision o recall?

R: El modelo tiene un mal recall (0.67), ya que este considera los falsos negativos, y vemos que existen múltiples predicciones de falso negativo, por lo que el modelo es peligroso al tener gente con diabetes no detectada.

Curva ROC 🤘¶

In [51]:
from sklearn.metrics import roc_curve, auc

y_scores = clf.predict_proba(X_test)[:,1]
fpr, tpr, _ = roc_curve(y_test, y_scores)
roc_auc = auc(fpr, tpr)

plt.plot(fpr, tpr, color='blue', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='gray', linestyle='--')  # Identidad
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Curva ROC')
plt.legend(loc="lower right")
plt.show()
No description has been provided for this image
  1. ¿Podemos decir que el modelo es bueno según su curva ROC? ¿Para que podemos usar esta visualización?

R: Según la curva ROC no es un mal modelo, pues el área bajo la curva es cercana a 1. Podemos utilizar esta visualización para evaluar el rendimiento, analizando el TPR y FPR.

  1. ¿Sería seguro usar este clasificador en un caso real diagnosicando pacientes?

R: Si bien el modelo tiene un buen desempeño en cuanto a la curva ROC, consideramos que no es un modelo clasificador seguro para un caso real diagnosticando pacientes, porque en caso de que un paciente SÍ tenga diabetes, el modelo sólo va a predecir correctamente un 67% de las veces.

Y eso es todo por hoy :)